#include "Terrain/TerrainContentsGeneratorFlat.h"
#include "Terrain/SimplexNoise.h"
#include "Terrain/ScSimplexNoise.h"

#include <algorithm>
#include "Engine/MathUtils.hpp"
#include "Engine/Random.hpp"
#include "Blocks/FluidBlock.h"

#include "Managers/BlocksManager.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "GameSingleton.h"

using namespace PocketSurvival::Engine;

namespace PocketSurvival
{
    TerrainContentsGeneratorFlat::~TerrainContentsGeneratorFlat() {}

    uint32_t TerrainContentsGeneratorFlat::getOceanLevel()
    {
        return m_subsystemGameInfo->terrainLevel + m_subsystemGameInfo->seaLevelOffset;
    }

    TerrainContentsGeneratorFlat::TerrainContentsGeneratorFlat()
    {
        m_subsystemGameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();

        m_oceanCorner = Vector2(-501.0f, -501.0f);
        m_shoreRoughnessAmplitude.posX = MathUtils::Pow(m_subsystemGameInfo->shoreRoughness, 2.0f) * (m_subsystemGameInfo->terrainGenerationMode == TerrainGenerationMode::FlatIsland ? MathUtils::Min<float>(4.0f * m_subsystemGameInfo->islandSize.posX, 400.0f) : 400.0f);
        m_shoreRoughnessAmplitude.posY = MathUtils::Pow(m_subsystemGameInfo->shoreRoughness, 2.0f) * (m_subsystemGameInfo->terrainGenerationMode == TerrainGenerationMode::FlatIsland ? MathUtils::Min<float>(4.0f * m_subsystemGameInfo->islandSize.posY, 400.0f) : 400.0f);
        m_shoreRoughnessFrequency = MathUtils::Lerp(0.5f, 1.0f, m_subsystemGameInfo->shoreRoughness) * Vector2(1.0f) / m_shoreRoughnessAmplitude;
        m_shoreRoughnessOctaves.posX = (int32_t)MathUtils::Clamp<float>(MathUtils::Log(1.0f / m_shoreRoughnessFrequency.posX) / MathUtils::Log(2.0f) - 1.0f, 1.0f, 7.0f);
        m_shoreRoughnessOctaves.posY = (int32_t)MathUtils::Clamp<float>(MathUtils::Log(1.0f / m_shoreRoughnessFrequency.posY) / MathUtils::Log(2.0f) - 1.0f, 1.0f, 7.0f);

        Random rd(m_subsystemGameInfo->worldSeed);
        m_shoreRoughnessOffset[0] = rd.getFloat(-2000.0f, 2000.0f);
        m_shoreRoughnessOffset[1] = rd.getFloat(-2000.0f, 2000.0f);
        m_shoreRoughnessOffset[2] = rd.getFloat(-2000.0f, 2000.0f);
        m_shoreRoughnessOffset[3] = rd.getFloat(-2000.0f, 2000.0f);
    }

    Vector3 TerrainContentsGeneratorFlat::findCoarseSpawnPosition()
    {
        for (int32_t i = -400; i <= 400; i += 10)
        {
            for (int32_t j = -400; j <= 400; j += 10)
            {
                Vector2 vector = m_oceanCorner + Vector2(i, j);
                float num = calculateOceanShoreDistance(vector.posX, vector.posY);
                if (num >= 1.0f && num <= 20.0f)
                {
                    return Vector3(vector.posX, calculateHeight(vector.posX, vector.posY), vector.posY);
                }
            }
        }
        return Vector3(m_oceanCorner.posX, calculateHeight(m_oceanCorner.posX, m_oceanCorner.posY), m_oceanCorner.posY);
    }

    float TerrainContentsGeneratorFlat::calculateOceanShoreDistance(float x, float z)
    {
        float x2 = 0.0;
        float x3 = 0.0f;
        float y = 0.0f;
        float y2 = 0.0f;
        if (m_shoreRoughnessAmplitude.posX > 0.0f)
        {
            x2 = m_shoreRoughnessAmplitude.posX * ScSimplexNoise::OctavedNoise(z + m_shoreRoughnessOffset[0], m_shoreRoughnessFrequency.posX, (int32_t)m_shoreRoughnessOctaves.posX, 2.0f, 0.6f);
            x3 = m_shoreRoughnessAmplitude.posX * ScSimplexNoise::OctavedNoise(z + m_shoreRoughnessOffset[1], m_shoreRoughnessFrequency.posX, (int32_t)m_shoreRoughnessOctaves.posX, 2.0f, 0.6f);
        }
        if (m_shoreRoughnessAmplitude.posY > 0.0f)
        {
            y = m_shoreRoughnessAmplitude.posY * ScSimplexNoise::OctavedNoise(x + m_shoreRoughnessOffset[2], m_shoreRoughnessFrequency.posY, (int32_t)m_shoreRoughnessOctaves.posY, 2.0f, 0.6f);
            y2 = m_shoreRoughnessAmplitude.posY * ScSimplexNoise::OctavedNoise(x + m_shoreRoughnessOffset[3], m_shoreRoughnessFrequency.posY, (int32_t)m_shoreRoughnessOctaves.posY, 2.0f, 0.6f);
        }
        Vector2 vector = m_oceanCorner + Vector2(x2, y);
        Vector2 vector2 = m_oceanCorner + (m_subsystemGameInfo->terrainGenerationMode == TerrainGenerationMode::FlatIsland ? m_subsystemGameInfo->islandSize : Vector2(3.40282347E+38f)) + Vector2(x3, y2);
        return MathUtils::Min<float>(x - vector.posX, vector2.posX - x, z - vector.posY, vector2.posY - z);

        // return MathUtils::Min<float>(x,z);
    }

    float TerrainContentsGeneratorFlat::calculateHeight(float x, float z)
    {
        return m_subsystemGameInfo->terrainLevel;
    }

    int32_t TerrainContentsGeneratorFlat::calculateTemperature(float x, float z)
    {
        return MathUtils::Clamp<int32_t>(12 + (int32_t)m_subsystemGameInfo->temperatureOffset, 0, 15);
    }

    int32_t TerrainContentsGeneratorFlat::calculateHumidity(float x, float z)
    {
        return MathUtils::Clamp<int32_t>(12 + (int32_t)m_subsystemGameInfo->humidityOffset, 0, 15);
    }

    float TerrainContentsGeneratorFlat::calculateMountainRangeFactor(float x, float z)
    {
        return 0.0f;
    }

    void TerrainContentsGeneratorFlat::generateChunkContentsPass1(TerrainChunk *chunk)
    {
        for (int32_t i = 0; i < 16; i++)
        {
            for (int32_t j = 0; j < 16; j++)
            {
                int32_t num = i + chunk->origin.posX;
                int32_t num2 = j + chunk->origin.posY;
                chunk->setTemperatureFast(i, j, calculateTemperature(num, num2));
                chunk->setHumidityFast(i, j, calculateHumidity(num, num2));
                float distance = calculateOceanShoreDistance(num, num2);
                bool flag = distance >= 0.0f;
                int32_t num3 = TerrainChunk::CalculateCellIndex(i, 0, j);
                for (int32_t k = 0; k < 256; k++)
                {
                    int32_t value = Terrain::MakeBlockValue(0);
                    if (flag)
                    {
                        if (k < 5)
                        {
                            value = Terrain::MakeBlockValue(1);
                        }
                        else if (k < m_subsystemGameInfo->terrainLevel)
                        {
                            value = Terrain::MakeBlockValue((m_subsystemGameInfo->terrainBlockIndex == 8) ? 2 : m_subsystemGameInfo->terrainBlockIndex);
                        }
                        else if (k == m_subsystemGameInfo->terrainLevel)
                        {
                            value = Terrain::MakeBlockValue(m_subsystemGameInfo->terrainBlockIndex);
                        }
                        else if (k <= getOceanLevel())
                        {
                            value = Terrain::MakeBlockValue(m_subsystemGameInfo->terrainOceanBlockIndex);
                        }
                    }
                    else if (k < 5)
                    {
                        value = Terrain::MakeBlockValue(1);
                    }
                    else if (k <= getOceanLevel())
                    {
                        value = Terrain::MakeBlockValue(m_subsystemGameInfo->terrainOceanBlockIndex);
                    }
                    chunk->setCellValueFast(num3 + k, value);
                }
            }
        }
    }

    void TerrainContentsGeneratorFlat::generateChunkContentsPass2(TerrainChunk *chunk)
    {
        updateFluidIsTop(chunk);
    }

    void TerrainContentsGeneratorFlat::generateChunkContentsPass3(TerrainChunk *chunk)
    {
    }

    void TerrainContentsGeneratorFlat::generateChunkContentsPass4(TerrainChunk *chunk)
    {
    }

    void TerrainContentsGeneratorFlat::updateFluidIsTop(TerrainChunk *chunk)
    {
        for (int32_t i = 0; i < 16; i++)
        {
            for (int32_t j = 0; j < 16; j++)
            {
                int32_t num = TerrainChunk::CalculateCellIndex(i, 255, j);
                int32_t num2 = 0;
                int32_t num3 = 255;
                while (num3 >= 0)
                {
                    int32_t cellValueFast = chunk->getCellValueFast(num);
                    int32_t content = Terrain::ExtractContents(cellValueFast);

                    if (content != 0 && content != num2 && GameSingleton::blocksManager->blocks[content]->isBlock<FluidBlock *>())
                    {
                        int32_t data = Terrain::ExtractData(cellValueFast);
                        chunk->setCellValueFast(num, Terrain::MakeBlockValue(content, 0, FluidBlock::SetIsTop(data, true)));
                    }
                    num2 = content;
                    num3--;
                    num--;
                }
            }
        }
    }

}